<!--This is the plain html source of the hex encoded Editor-Page embedded in SPIFFSEditor.cpp -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>ESP Editor</title>
<style type="text/css" media="screen">
.cm {
  z-index: 300;
  position: absolute;
  left: 5px;
  border: 1px solid #444;
  background-color: #F5F5F5;
  display: none;
  box-shadow: 0 0 10px rgba( 0, 0, 0, .4 );
  font-size: 12px;
  font-family: sans-serif;
  font-weight:bold;
}
.cm ul {
  list-style: none;
  top: 0;
  left: 0;
  margin: 0;
  padding: 0;
}
.cm li {
  position: relative;
  min-width: 60px;
  cursor: pointer;
}
.cm span {
  color: #444;
  display: inline-block;
  padding: 6px;
}
.cm li:hover { background: #444; }
.cm li:hover span { color: #EEE; }
.tvu ul, .tvu li {
  padding: 0;
  margin: 0;
  list-style: none;
}
.tvu input {
  position: absolute;
  opacity: 0;
}
.tvu {
  font: normal 12px Verdana, Arial, Sans-serif;
  -moz-user-select: none;
  -webkit-user-select: none;
  user-select: none;
  color: #444;
  line-height: 16px;
}
.tvu span {
  margin-bottom:5px;
  padding: 0 0 0 18px;
  cursor: pointer;
  display: inline-block;
  height: 16px;
  vertical-align: middle;
  background: url('') no-repeat;
  background-position: 0px 0px;
}
.tvu span:hover {
  text-decoration: underline;
}
@media screen and (-webkit-min-device-pixel-ratio:0){
  .tvu{
    -webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;
  }

  @-webkit-keyframes webkit-adjacent-element-selector-bugfix {
    from { 
      padding: 0;
    } 
    to { 
      padding: 0;
    }
  }
}
#uploader { 
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height:28px;
  line-height: 24px;
  padding-left: 10px;
  background-color: #444;
  color:#EEE;
}
#tree { 
  position: absolute;
  top: 28px;
  bottom: 0;
  left: 0;
  width:160px;
  padding: 8px;
}
#editor, #preview { 
  position: absolute;
  top: 28px;
  right: 0;
  bottom: 0;
  left: 160px;
  border-left:1px solid #EEE;
}
#preview {
  background-color: #EEE;
  padding:5px;
}
#loader { 
  position: absolute;
  top: 36%;
  right: 40%;
}
.loader {
    z-index: 10000;
    border: 8px solid #b5b5b5; /* Grey */
    border-top: 8px solid #3498db; /* Blue */
    border-bottom: 8px solid #3498db; /* Blue */
    border-radius: 50%;
    width: 240px;
    height: 240px;
    animation: spin 2s linear infinite;
    display:none;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}
</style>
<script>
if (typeof XMLHttpRequest === "undefined") {
  XMLHttpRequest = function () {
    try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
    try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
    try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
    throw new Error("This browser does not support XMLHttpRequest.");
  };
}

function ge(a){
  return document.getElementById(a);
}
function ce(a){
  return document.createElement(a);
}

function sortByKey(array, key) {
  return array.sort(function(a, b) {
    var x = a[key]; var y = b[key];
    return ((x < y) ? -1 : ((x > y) ? 1 : 0));
  });
}


var QueuedRequester = function () {
  this.queue = [];
  this.running = false;
  this.xmlhttp = null;
}
QueuedRequester.prototype = {
  _request: function(req){
    this.running = true;
    if(!req instanceof Object) return;
    var that = this;
    
    function ajaxCb(x,d){ return function(){
      if (x.readyState == 4){
        ge("loader").style.display = "none";
        d.callback(x.status, x.responseText);
        if(that.queue.length === 0) that.running = false;
        if(that.running) that._request(that.queue.shift());
      }
    }}
    
    ge("loader").style.display = "block";
    
    var p = "";
    if(req.params instanceof FormData){
      p = req.params;
    } else if(req.params instanceof Object){
      for (var key in req.params) {
        if(p === "")
          p += (req.method === "GET")?"?":"";
        else
          p += "&";
        p += encodeURIComponent(key)+"="+encodeURIComponent(req.params[key]);
      };
    }
    
    this.xmlhttp = new XMLHttpRequest();
    this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req);
    if(req.method === "GET"){
      this.xmlhttp.open(req.method, req.url+p, true);
      this.xmlhttp.send();
    } else {
      this.xmlhttp.open(req.method, req.url, true);
      if(p instanceof String)
        this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      this.xmlhttp.send(p);
    }
  },
  stop: function(){
    if(this.running) this.running = false;
    if(this.xmlhttp && this.xmlhttp.readyState < 4){
      this.xmlhttp.abort();
    }
  },
  add: function(method, url, params, callback){
    this.queue.push({url:url,method:method,params:params,callback:callback});
    if(!this.running){
      this._request(this.queue.shift());
    }
  }
}

var requests = new QueuedRequester();

function createFileUploader(element, tree, editor){
  var xmlHttp;
  
  var refresh = ce("button");
  refresh.innerHTML = 'Refresh List';
  ge(element).appendChild(refresh);

  var input = ce("input");
  input.type = "file";
  input.multiple = false;
  input.name = "data";
  input.id="upload-select";
  ge(element).appendChild(input);
  
  var path = ce("input");
  path.id = "upload-path";
  path.type = "text";
  path.name = "path";
  path.defaultValue = "/";
  ge(element).appendChild(path);
  
  var button = ce("button");
  button.innerHTML = 'Upload';
  ge(element).appendChild(button);
  
  var mkfile = ce("button");
  mkfile.innerHTML = 'Create';
  ge(element).appendChild(mkfile);

  var filename     = ce("input");
  filename.id      = "editor-filename";
  filename.type    = "text";
  filename.disabled= true;
  filename.size    = 20;
  ge(element).appendChild(filename);

  var savefile = ce("button");
  savefile.innerHTML = ' Save ' ;
  ge(element).appendChild(savefile);

  function httpPostProcessRequest(status, responseText){
    if(status != 200)
      alert("ERROR["+status+"]: "+responseText);
    else
      tree.refreshPath(path.value);
  }
  function createPath(p){
    var formData = new FormData();
    formData.append("path", p);
    requests.add("PUT", "/edit", formData, httpPostProcessRequest);
  }
  
  mkfile.onclick = function(e){
    createPath(path.value);
    editor.loadUrl(path.value);
    path.value="/";
  };

  savefile.onclick = function(e){
    editor.execCommand('saveCommand');
  };
  
  refresh.onclick = function(e){
    tree.refreshPath(path.value);
  };
  
  button.onclick = function(e){
    if(input.files.length === 0){
      return;
    }
    var formData = new FormData();
    formData.append("data", input.files[0], path.value);
    requests.add("POST", "/edit", formData, httpPostProcessRequest);
    var uploadPath= ge("upload-path");
    uploadPath.value="/";
    var uploadSelect= ge("upload-select");
    uploadSelect.value="";
  };
  input.onchange = function(e){
    if(input.files.length === 0) return;
    var filename = input.files[0].name;
    var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
    var name = /(.*)\.[^.]+$/.exec(filename)[1];
    if(typeof name !== undefined){
      filename = name;
    }
    path.value = "/"+filename+"."+ext;
  };
}

function createTree(element, editor){
  var preview = ge("preview");
  var treeRoot = ce("div");
  treeRoot.className = "tvu";
  ge(element).appendChild(treeRoot);

  function loadDownload(path){
    ge('download-frame').src = "/edit?download="+path;
  }

  function loadPreview(path){
    var edfname = ge("editor-filename");
    edfname.value=path;
    ge("editor").style.display = "none";
    preview.style.display = "block";
    preview.innerHTML = '<img src="/edit?edit='+path+'&_cb='+Date.now()+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />';
  }

  function fillFileMenu(el, path){
    var list = ce("ul");
    el.appendChild(list);
    var action = ce("li");
    list.appendChild(action);
    if(isImageFile(path)){
      action.innerHTML = "<span>Preview</span>";
      action.onclick = function(e){
        loadPreview(path);
        if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
      };
    } else if(isTextFile(path)){
      action.innerHTML = "<span>Edit</span>";
      action.onclick = function(e){
        editor.loadUrl(path);
        if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
      };
    }
    var download = ce("li");
    list.appendChild(download);
    download.innerHTML = "<span>Download</span>";
    download.onclick = function(e){
      loadDownload(path);
      if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
    };
    var delFile = ce("li");
    list.appendChild(delFile);
    delFile.innerHTML = "<span>Delete</span>";
    delFile.onclick = function(e){
      httpDelete(path);
      if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
    };
  }

  function showContextMenu(event, path, isfile){
    var divContext = ce("div");
    var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;
    var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft;
    var left = event.clientX + scrollLeft;
    var top = event.clientY + scrollTop;
    divContext.className = 'cm';
    divContext.style.display = 'block';
    divContext.style.left = left + 'px';
    divContext.style.top = top + 'px';
    fillFileMenu(divContext, path);
    document.body.appendChild(divContext);
    var width = divContext.offsetWidth;
    var height = divContext.offsetHeight;
    divContext.onmouseout = function(e){
      if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){
        if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(divContext);
      }
    };
  }

  function createTreeLeaf(path, name, size){
    var leaf = ce("li");
    leaf.id = name;
    var label = ce("span");
    label.innerHTML = name;
    leaf.appendChild(label);
    leaf.onclick = function(e){
      if(isTextFile(leaf.id.toLowerCase())){
        editor.loadUrl(leaf.id);
      } else if(isImageFile(leaf.id.toLowerCase())){
        loadPreview(leaf.id);
      }
    };
    leaf.oncontextmenu = function(e){
      e.preventDefault();
      e.stopPropagation();
      showContextMenu(e, leaf.id, true);
    };
    return leaf;
  }

  function addList(parent, path, items){
    sortByKey(items, 'name');
    var list = ce("ul");
    parent.appendChild(list);
    var ll = items.length;
    for(var i = 0; i < ll; i++){
      if(items[i].type === "file")
        list.appendChild(createTreeLeaf(path, items[i].name, items[i].size));
    }

  }

  function isTextFile(path){
    var ext = /(?:\.([^.]+))?$/.exec(path)[1];
    if(typeof ext !== undefined){
      switch(ext){
        case "txt":
        case "htm":
        case "html":
        case "js":
        case "css":
        case "xml":
        case "json":
        case "conf":
        case "ini":
        case "h":
        case "c":
        case "cpp":
        case "php":
        case "hex":
        case "ino":
        case "pde":
        return true;
      }
    }
    return false;
  }

  function isImageFile(path){
    var ext = /(?:\.([^.]+))?$/.exec(path)[1];
    if(typeof ext !== undefined){
      switch(ext){
        case "png":
        case "jpg":
        case "gif":
        case "bmp":
        return true;
      }
    }
    return false;
  }

  this.refreshPath = function(path){
    treeRoot.removeChild(treeRoot.childNodes[0]);
    httpGet(treeRoot, "/");
  };

  function delCb(path){
    return function(status, responseText){
      if(status != 200){
        alert("ERROR["+status+"]: "+responseText);
      } else {
        treeRoot.removeChild(treeRoot.childNodes[0]);
        httpGet(treeRoot, "/");
      }
    }
  }

  function httpDelete(filename){
    var formData = new FormData();
    formData.append("path", filename);
    requests.add("DELETE", "/edit", formData, delCb(filename));
  }

  function getCb(parent, path){
    return function(status, responseText){
      if(status == 200)
        addList(parent, path, JSON.parse(responseText));
    }
  }

  function httpGet(parent, path){
    requests.add("GET", "/edit", { list: path }, getCb(parent, path));
  }

  httpGet(treeRoot, "/");
  return this;
}

function createEditor(element, file, lang, theme, type){
  function getLangFromFilename(filename){
    var lang = "plain";
    var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
    if(typeof ext !== undefined){
      switch(ext){
        case "txt": lang = "plain"; break;
        case "hex": lang = "plain"; break;
        case "conf": lang = "plain"; break;
        case "htm": lang = "html"; break;
        case "js": lang = "javascript"; break;
        case "h": lang = "c_cpp"; break;
        case "c": lang = "c_cpp"; break;
        case "cpp": lang = "c_cpp"; break;
        case "css":
        case "scss":
        case "php":
        case "html":
        case "json":
        case "xml":
        case "ini":  lang = ext;
      }
    }
    return lang;
  }

  if(typeof file === "undefined") file = "/index.html";

  if(typeof lang === "undefined"){
    lang = getLangFromFilename(file);
  }

  if(typeof theme === "undefined") theme = "textmate";

  if(typeof type === "undefined"){
    type = "text/"+lang;
    if(lang === "c_cpp") type = "text/plain";
  }

  var editor = ace.edit(element);
  function httpPostProcessRequest(status, responseText){
    if(status != 200) alert("ERROR["+status+"]: "+responseText);
  }
  function httpPost(filename, data, type){
    var formData = new FormData();
    formData.append("data", new Blob([data], { type: type }), filename);
    requests.add("POST", "/edit", formData, httpPostProcessRequest);
  }
  function httpGetProcessRequest(status, responseText){
      ge("preview").style.display = "none";
      ge("editor").style.display = "block";
      if(status == 200)
        editor.setValue(responseText);
      else
        editor.setValue("");
      editor.clearSelection();
  }
  function httpGet(theUrl){
      requests.add("GET", "/edit", { edit: theUrl }, httpGetProcessRequest);
  }

  if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
  editor.setTheme("ace/theme/"+theme);
  editor.$blockScrolling = Infinity;
  editor.getSession().setUseSoftTabs(true);
  editor.getSession().setTabSize(2);
  editor.setHighlightActiveLine(true);
  editor.setShowPrintMargin(false);
  editor.commands.addCommand({
      name: 'saveCommand',
      bindKey: {win: 'Ctrl-S',  mac: 'Command-S'},
      exec: function(editor) {
        httpPost(file, editor.getValue()+"", type);
      },
      readOnly: false
  });
  editor.commands.addCommand({
      name: 'undoCommand',
      bindKey: {win: 'Ctrl-Z',  mac: 'Command-Z'},
      exec: function(editor) {
        editor.getSession().getUndoManager().undo(false);
      },
      readOnly: false
  });
  editor.commands.addCommand({
      name: 'redoCommand',
      bindKey: {win: 'Ctrl-Shift-Z',  mac: 'Command-Shift-Z'},
      exec: function(editor) {
        editor.getSession().getUndoManager().redo(false);
      },
      readOnly: false
  });
  editor.loadUrl = function(filename){
    var edfname = ge("editor-filename");
    edfname.value=filename;
    file = filename;
    lang = getLangFromFilename(file);
    type = "text/"+lang;
    if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
    httpGet(file);
  };
  return editor;
}
function onBodyLoad(){
  var vars = {};
  var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; });
  var editor = createEditor("editor", vars.file, vars.lang, vars.theme);
  var tree = createTree("tree", editor);
  createFileUploader("uploader", tree, editor);
  if(typeof vars.file === "undefined") vars.file = "/index.htm";
  editor.loadUrl(vars.file);
};
</script>
<script id='ace' src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
  if  (typeof ace.edit == "undefined") {
    var script = document.createElement('script');
    script.src = "/ace.js";
    script.async = false;
    document.head.appendChild(script);
  }
</script>
</head>
<body onload="onBodyLoad();">
  <div id="loader" class="loader"></div>
  <div id="uploader"></div>
  <div id="tree"></div>
  <div id="editor"></div>
  <div id="preview" style="display:none;"></div>
  <iframe id=download-frame style='display:none;'></iframe>
</body>
</html>
